﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using EntityComponentSystem.Framework.Systems;
using System.IO;
using EntityComponentSystem.Framework.Json;

namespace EntityComponentSystem.Framework
{
    public class GameEngine : IDisposable
    {
        private List<Behavior> behaviors = new List<Behavior>();
        private List<ISystem> systems = new List<ISystem>();
        private List<IBehaviorSystem> behaviorSystems = new List<IBehaviorSystem>();

        private IntPool entityPool = new IntPool(32);
        private List<ComponentList> entityComponents = new List<ComponentList>(32);
        private List<BehaviorList> entityBehaviors = new List<BehaviorList>(32);
        private List<TagList> entityTags = new List<TagList>();
        private Dictionary<string, List<int>> entityTagMap = new Dictionary<string, List<int>>();
        private List<IAutoSystem> autoSystem = new List<IAutoSystem>();

        public float TimeElasped { get; private set; }
        public float TimeSinceLastFrame { get; private set; }
        public long TimeSinceLastFrameL { get; private set; }

        public void RegisterSystem(ISystem system)
        {
            systems.Add(system);
            if (system is IBehaviorSystem)
                behaviorSystems.Add((IBehaviorSystem)system);
            if (system is IAutoSystem)
                autoSystem.Add((IAutoSystem)system);
        }

        public void RegisterBehavior(Behavior behavior)
        {
            behaviors.Add(behavior);
        }

        public int CreateEntity()
        {
            var nextid = entityPool.Get();
            while (nextid >= entityComponents.Count)
                entityComponents.Add(new ComponentList(nextid));

            while (nextid >= entityBehaviors.Count)
                entityBehaviors.Add(new BehaviorList(nextid));

            while (nextid >= entityTags.Count)
                entityTags.Add(new TagList(nextid));

            return nextid;
        }

        public int CreateEntity(string file)
        {
            if (System.IO.File.Exists(file))
            {
                using (var sr = new StreamReader(file))
                {
                    var json = JsonReader.ParseObject(sr.ReadToEnd());

                    if (json != null)
                    {
                        var entity = CreateEntity();
                        var tags = (JsonArray)json["tags"];
                        var components = (JsonObject)json["components"];
                        var behaviors = (JsonArray)json["behaviors"];

                        foreach (var key in components.Keys)
                        {
                            var com = new Component();
                            var jcomponent = (JsonObject)components[key];
                            foreach (var ckey in jcomponent.Keys)
                                com[ckey] = jcomponent[ckey];

                            AddComponent(entity, key, com);
                        }

                        for (var i = 0; i < behaviors.Count; i++)
                            AddBehavior(entity, behaviors[i]);

                        for (var i = 0; i < tags.Count; i++)
                            AddTag(entity, tags[i]);

                        return entity;
                    }
                }
            }
            return -1;
        }

        public void DeleteEntity(int entity)
        {
            entityComponents[entity].Clear(this);
            entityBehaviors[entity].Clear(this);
            entityPool.Free(entity);
        }

        public int GetEntityByTag(string tag)
        {
            List<int> tagMap;
            if (entityTagMap.TryGetValue(tag, out tagMap))
                if (tagMap.Count > 0)
                    return tagMap[0];
            return -1;
        }

        public IEnumerable<int> GetEntitiesByTag(string tag)
        {
            List<int> tagMap;
            if (entityTagMap.TryGetValue(tag, out tagMap))
                for (var i = 0; i < tagMap.Count; i++)
                    yield return tagMap[i];
        }

        public void AddTag(int entity, string tag)
        {
            entityTags[entity].Add(this, tag);

            List<int> tagMap;
            if (!entityTagMap.TryGetValue(tag, out tagMap))
            {
                tagMap = new List<int>();
                entityTagMap.Add(tag, tagMap);
            }

            tagMap.Add(entity);
        }

        public void RemoveTag(int entity, string tag)
        {
            entityTags[entity].Remove(this, tag);

            List<int> tagMap;
            if (entityTagMap.TryGetValue(tag, out tagMap))
            {
                tagMap.RemoveAll(x => x == entity);
                if (tagMap.Count == 0)
                    entityTagMap.Remove(tag);
            }
        }

        public void AddBehavior(int entity, string behavior)
        {
            entityBehaviors[entity].Add(this, behavior);
        }

        public void RemoveBehavior(int entity, string behavior)
        {
            entityBehaviors[entity].Remove(this, behavior);
        }

        internal void AddToAutoSystems(int entity, string name)
        {
            for (var i = 0; i < autoSystem.Count; i++)
            {
                for (var k = 0; k < autoSystem[i].AutoMappedComponents.Length; k++)
                {
                    if (autoSystem[i].AutoMappedComponents[k] == name)
                    {
                        autoSystem[i].AddEntity(entity);
                        break;
                    }
                }
            }
        }

        internal void RemoveFromAutoSystems(int entity, string name)
        {
            for (var i = 0; i < autoSystem.Count; i++)
            {
                for (var k = 0; k < autoSystem[i].AutoMappedComponents.Length; k++)
                {
                    if (autoSystem[i].AutoMappedComponents[k] == name)
                    {
                        autoSystem[i].RemoveEntity(entity);
                        break;
                    }
                }
            }
        }

        public void AddComponent(int entity, string name, Component component)
        {
            entityComponents[entity].Add(this, name, component);
        }

        public void RemoveComponent(int entity, string component)
        {
            entityComponents[entity].Remove(this, component);
        }

        public Component GetComponent(int entity, string name)
        {
            if (entity > -1 && entity < entityComponents.Count)
                return entityComponents[entity].Find(name);
            return null;
        }

        public T GetComponent<T>(int entity, string name)
            where T : Component
        {
            if (entity > -1 && entity < entityComponents.Count)
                return entityComponents[entity].Find<T>(name);
            return null;
        }

        public void SetComponent(int entity, string name, string variable, JsonValue val)
        {
            var com = GetComponent(entity, name);
            if (com != null)
                com[variable] = val;
        }

        public T GetSystem<T>(string name)
            where T : ISystem
        {
            for (var i = 0; i < systems.Count; i++)
                if (systems[i].Name == name)
                    return (T)systems[i];

            return default(T);
        }

        public T GetBehaviorSystem<T>(string name)
            where T : IBehaviorSystem
        {
            for (var i = 0; i < behaviorSystems.Count; i++)
                if (behaviorSystems[i].Name == name)
                    return (T)behaviorSystems[i];

            return default(T);
        }

        public Behavior GetBehavior(string name)
        {
            return behaviors.FirstOrDefault(x => x.Name == name);
        }

        public void Update(long ms)
        {
            Update(ms / 1000f);
        }

        public void Update(float delta)
        {
            TimeElasped += delta;
            TimeSinceLastFrame = delta;
            TimeSinceLastFrameL = (long)(delta * 1000);
            foreach (var system in systems)
                system.Update(this);
        }

        public void Dispose()
        {
            //delete all entities
        }

    }
}
